package parsing.productions;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

/**
 * Subclasses of AbstractNode represent productions in the program.
 */
public abstract class AbstractNode implements Node {
	
	/**
	 * The children Nodes of this Node.
	 */
	protected List<AbstractNode> children;

	/**
	 * Constructor: creates an instance of AbstractNode no children.
	 */
	public AbstractNode() {
		children= new LinkedList<AbstractNode>();
	}
	
	public List<AbstractNode> getChildren(){
		return children;
	}
	
	@Override
	public int size() {
		int nodeSize= 1;
		for (Node n : children) {
			nodeSize= nodeSize + n.size();
		}
		return nodeSize;
	}

	@Override
	public abstract void prettyPrint(StringBuffer sb);
	
	@Override
	public abstract Node duplicate();
	
	/**
	 * Adds this node and all of this node's children to toAppend
	 * @param toAppend the list to add all the nodes to
	 */
	public void addTree(List<AbstractNode> toAppend){
		toAppend.add(this);
		for(AbstractNode n : children){
			n.addTree(toAppend);
		}
	}
	
	/**
	 * Checks if this node's children are the same as n's 
	 * @param n the node to check against
	 * @return true if both children lists contain equal things
	 */
	@Override
	public boolean equals(Node n){
		if(!(n instanceof AbstractNode))
			return false;
		AbstractNode aNode = (AbstractNode) n;
		if(children.size() != aNode.children.size())
			return false;
		for(int i = 0; i < children.size(); i++)
			if(!children.get(i).equals(aNode.children.get(i)))
					return false;
		return true;
	}

	/**
	 * With equal probability, either chooses a random Term from genome, or randomly
	 * generates a new Term, and makes sure that the Term returned is not equal to original
	 * Exception: if choosing from genome and genome only contains one Term, that Term will
	 * be used even if it is equal to original.
	 * @param genome the Program to possibly select from
	 * @param original the Term to compare to
	 * @return the new Term
	 */
	public static Term generateTerm(Program genome, AbstractNode original) {
		Random rnd = new Random();
		int mutationType = rnd.nextInt(2);
		Term rtn;
		if(mutationType == 0){//choose a random Term
			rtn = chooseTerm(genome, original);
		} else {//generate a random Term
			List<AbstractNode> rtnChildren = new LinkedList<AbstractNode>();
			List<AbstractNode> factorChildren = new LinkedList<AbstractNode>();
			int factorType = rnd.nextInt(2);
			if(factorType == 0){
				factorChildren.add(new Atom(0));
				rtnChildren.add(new Factor(factorChildren));
			} else {
				rtnChildren.add(chooseFactor(genome, null));
			}
			int prob = rnd.nextInt(2);
			while(prob == 1){
				rtnChildren.add(new AddOp(rnd.nextInt(2) + 1));
				factorChildren.clear();
				factorType = rnd.nextInt(2);
				if(factorType == 0){
					factorChildren.add(new Atom(0));
					rtnChildren.add(new Factor(factorChildren));
				} else {
					rtnChildren.add(chooseFactor(genome, null));
				}
				prob = rnd.nextInt(2);
			}
			rtn = new Term(rtnChildren);
		}
		return rtn;
	}
	
	/**
	 * Chooses a random Term from genome and makes sure that the Term returned is not equal to original
	 * Exception: if choosing from genome and genome only contains one Term, that Term will
	 * be used even if it is equal to original.
	 * @param genome the Program to possibly select from
	 * @param original the Term to compare to
	 * @return the new Term
	 * Requires: original is a Term, or null
	 */
	public static Term chooseTerm(Program genome, AbstractNode original){
		Random rnd = new Random();
		List<AbstractNode> nodeList = new LinkedList<AbstractNode>();
		genome.addTree(nodeList);
		List<Term> termList = new LinkedList<Term>();
		for(AbstractNode n : nodeList){
			if(n instanceof Term)
				termList.add((Term) n);
		}
		if(termList.size() == 0)
			return (Term) original;
		if(termList.size() == 1)
			return (Term) termList.get(0).duplicate();
		int randomTerm;
		Term rtn;
		randomTerm = rnd.nextInt(termList.size());
		rtn = (Term) termList.get(randomTerm).duplicate();
		return rtn;
	}
	
	/**
	 * Chooses a random Factor from genome and makes sure that the Factor returned is not equal to original
	 * Exception: if choosing from genome and genome only contains one Factor, that Factor will
	 * be used even if it is equal to original.
	 * @param genome the Program to possibly select from
	 * @param original the Factor to compare to
	 * @return the new Factor
	 * Requires: original is a Factor, or null
	 */
	public static Factor chooseFactor(Program genome, AbstractNode original){
		Random rnd = new Random();
		List<AbstractNode> nodeList = new LinkedList<AbstractNode>();
		genome.addTree(nodeList);
		List<Factor> factorList = new LinkedList<Factor>();
		for(AbstractNode n : nodeList){
			if(n instanceof Factor)
				factorList.add((Factor) n);
		}
		if(factorList.size() == 0)
			return (Factor) original;
		if(factorList.size() == 1)
			return (Factor) factorList.get(0).duplicate();
		int randomFactor;
		Factor rtn;
		randomFactor = rnd.nextInt(factorList.size());
		rtn = (Factor) factorList.get(randomFactor).duplicate();
		return rtn;
	}
	
	/**
	 * Chooses a random Atom from genome and makes sure that the Atom returned is not equal to original
	 * Exception: if choosing from genome and genome only contains one Atom, that Atom will
	 * be used even if it is equal to original.
	 * @param genome the Program to possibly select from
	 * @param original the Atom to compare to
	 * @return the new Atom
	 * Requires: original is an Atom, or null
	 */
	public static Atom chooseAtom(Program genome, AbstractNode original){
		Random rnd = new Random();
		List<AbstractNode> nodeList = new LinkedList<AbstractNode>();
		genome.addTree(nodeList);
		List<Atom> atomList = new LinkedList<Atom>();
		for(AbstractNode n : nodeList){
			if(n instanceof Atom)
				atomList.add((Atom) n);
		}
		if(atomList.size() == 0)
			return (Atom) original;
		if(atomList.size() == 1)
			return (Atom) atomList.get(0).duplicate();
		int randomAtom;
		Atom rtn;
		randomAtom = rnd.nextInt(atomList.size());
		rtn = (Atom) atomList.get(randomAtom).duplicate();
		return rtn;
	}
	
	/**
	 * Chooses a random Condition from genome and makes sure that the Condition returned is not equal to original
	 * Exception: if choosing from genome and genome only contains one Condition, that Condition will
	 * be used even if it is equal to original.
	 * @param genome the Program to possibly select from
	 * @param original the Condition to compare to
	 * @return the new Condition
	 * Requires: original is an Condition, or null
	 */
	public static Condition chooseCondition(Program genome, AbstractNode original){
		Random rnd = new Random();
		List<AbstractNode> nodeList = new LinkedList<AbstractNode>();
		genome.addTree(nodeList);
		List<Condition> conditionList = new LinkedList<Condition>();
		for(AbstractNode n : nodeList){
			if(n instanceof Condition)
				conditionList.add((Condition) n);
		}
		if(conditionList.size() == 0)
			return (Condition) original;
		if(conditionList.size() == 1)
			return (Condition) conditionList.get(0).duplicate();
		int randomCondition;
		Condition rtn;
		randomCondition = rnd.nextInt(conditionList.size());
		rtn = (Condition) conditionList.get(randomCondition).duplicate();
		return rtn;
	}
	
	/**
	 * Chooses a random Conjunction from genome and makes sure that the Conjunction returned is not equal to original
	 * Exception: if choosing from genome and genome only contains one Conjunction, that Conjunction will
	 * be used even if it is equal to original.
	 * @param genome the Program to possibly select from
	 * @param original the Conjunction to compare to
	 * @return the new Conjunction
	 * Requires: original is an Conjunction, or null
	 */
	public static Conjunction chooseConjunction(Program genome, AbstractNode original){
		Random rnd = new Random();
		List<AbstractNode> nodeList = new LinkedList<AbstractNode>();
		genome.addTree(nodeList);
		List<Conjunction> conjunctionList = new LinkedList<Conjunction>();
		for(AbstractNode n : nodeList){
			if(n instanceof Conjunction)
				conjunctionList.add((Conjunction) n);
		}
		if(conjunctionList.size() == 0)
			return (Conjunction) original;
		if(conjunctionList.size() == 1)
			return (Conjunction) conjunctionList.get(0).duplicate();
		int randomConjunction;
		Conjunction rtn;
		randomConjunction = rnd.nextInt(conjunctionList.size());
		rtn = (Conjunction) conjunctionList.get(randomConjunction).duplicate();
		return rtn;
	}
	
	/**
	 * Chooses a random Relation from genome and makes sure that the Relation returned is not equal to original
	 * Exception: if choosing from genome and genome only contains one Relation, that Relation will
	 * be used even if it is equal to original.
	 * @param genome the Program to possibly select from
	 * @param original the Relation to compare to
	 * @return the new Relation
	 * Requires: original is an Relation, or null
	 */
	public static Relation chooseRelation(Program genome, AbstractNode original){
		Random rnd = new Random();
		List<AbstractNode> nodeList = new LinkedList<AbstractNode>();
		genome.addTree(nodeList);
		List<Relation> relationList = new LinkedList<Relation>();
		for(AbstractNode n : nodeList){
			if(n instanceof Relation)
				relationList.add((Relation) n);
		}
		if(relationList.size() == 0)
			return (Relation) original;
		if(relationList.size() == 1)
			return (Relation) relationList.get(0).duplicate();
		int randomRelation;
		Relation rtn;
		randomRelation = rnd.nextInt(relationList.size());
		rtn = (Relation) relationList.get(randomRelation).duplicate();
		return rtn;
	}
}
